查看原文
其他

深入剖析:优化,要从根源开始

韩锋 数据和云 2019-12-14
作者简介



编辑手记:韩老师在数据库性能优化方面有很丰富的经验,出版书籍《SQL 优化最佳实践》,感谢韩老师和机械工业出版社的授权,我们会在接下来的每周二分享书中的经典篇章,与大家共同成长。


今天与大家分享一则表结构设置不合理引发故障的案例。

案例说明

这是某公司后台的ERP系统,系统已经上线运行了10多年。随着时间的推移,累积的数据量越来越大。随着公司业务量的不断增加,数据库系统运行缓慢的问题日益凸显。为提高运行效率,公司计划有针对性地对部分大表进行数据清理。在DBA对某个大表进行清理时出现了问题。这个表本身有数百GB,按照指定的清理规则只需要根据主键字段范围(运算符为>=)选择出一定比例(不超过10%)的数据进行清理即可。但在实际使用中发现,该SQL是全表扫描,执行时间大大超出预期时间。DBA尝试使用强制指定索引方式清理数据,依然无效,整个SQL语句的执行效率达不到要求。


按照正常理解,执行索引范围扫描应该是效率较高的一种处理方式,但实际情况都是全表扫描。进一步分析发现,该表的主键是没有业务含义的,仅仅是自增长的数据,其来源是一个序列。但奇怪的是,这个主键字段的类型是变长文本类型,而不是通常的数字类型。当初定义该字段类型的依据,现在已经无从考证,但实验表明正是这个字段的类型“异常”,导致了错误的执行路径。


下面通过一个实验重现这个问题。

1、数据准备

两个表的数据类型相似(只是ID字段类型不同),各插入了320万数据,ID字段范围为1~3200000。



2、模拟场景

相关代码如下:



对于普通的采用数值类型的字段,范围查询就是正常的索引范围扫描,执行效率很高。



对于文本类型字段的表,范围查询就是对应的全表扫描,效率较低是显而易见的。


3、分析结论

字符类型在索引中是“乱序”的,这是因为字符类型的排序方式与我们的预期不同。从“select * from t2 where id>= '3199990'”执行返回755 565条记录可见,不是直观上的10条记录。这也是当初在做表设计时,开发人员没有注意的问题。


字符类型还导致了聚簇因子很大,原因是插入顺序与排序顺序不同。详细点说,就是按照数字类型插入(1..3200000),按字符类型('1'...'32000000')t排序。



在对字符类型使用大于运算符时,会导致优化器认为需要扫描索引大部分数据且聚簇因子很大,最终导致弃用索引扫描而改用全表扫描方式。


4、解决方法



将SQL语句由开放区间扫描(>=),修改为封闭区间(between xxx and max_value)。使得数据在索引局部顺序是“对的”。如果采用这种方式仍然走索引扫描,还可以进一步细化分段或者采用“逐条提取+批绑定”的方法。


案例启示

这是一个典型的由不好的数据类型带来的执行计划异常的例子。它给我们带来如下启示:

糟糕的数据结构设计往往是致命的,后期的优化只是补救措施。如果从源头上加以杜绝,这才是优化的根本。在设计初期能引入数据库审核,可以起到很好的作用。


如何加入"云和恩墨大讲堂"微信群

搜索 盖国强(Eygle)微信号:eyygle,或者扫描下面二维码,备注:云和恩墨大讲堂,即可入群。每周与千人共享免费技术分享,与讲师在线讨论。


近期文章

Oracle 11g Data Guard环境中的归档管理

一则library cache lock问题处理 

一则强行关库引发的蝴蝶效应

深入学习:In Memory Undo 

一则因内存导致的集群故障

巧妙kill CRS进程而不导致主机重启

insert into 太慢的问题

资源下载

关注本微信(OraNews)回复关键字获取

2016DTCC, 2016数据库大会PPT;

DBALife,"DBA的一天"精品海报大图;

12cArch,“Oracle 12c体系结构”精品海报;

DBA01,《Oracle DBA手记》第一本下载;

YunHe“云和恩墨大讲堂”案例文档下载;


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存